package l2p.loginserver.gameservercon;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.ArrayDeque;
import java.util.logging.Logger;

import javolution.util.FastList;
import l2p.Config;
import l2p.common.ThreadPoolManager;
import l2p.loginserver.GameServerTable;
import l2p.loginserver.LoginController;
import l2p.loginserver.crypt.ConnectionCrypt;
import l2p.loginserver.crypt.ConnectionCryptDummy;
import l2p.loginserver.crypt.NewCrypt;
import l2p.loginserver.gameservercon.gspackets.ClientBasePacket;
import l2p.loginserver.gameservercon.lspackets.KickPlayer;
import l2p.loginserver.gameservercon.lspackets.ServerBasePacket;
import l2p.loginserver.gameservercon.lspackets.TestConnection;
import l2p.util.Util;

public class AttGS
{
	private static final Logger log = Logger.getLogger(AttGS.class.getName());

	private final ByteBuffer readBuffer = ByteBuffer.allocate(64 * 1024).order(ByteOrder.LITTLE_ENDIAN);
	private final ArrayDeque<ServerBasePacket> sendQueue = new ArrayDeque<ServerBasePacket>();
	private final FastList<String> accountsInGameServer = FastList.newInstance();

	private final SelectionKey key;
	private RSACrypt rsa;
	private ConnectionCrypt crypt;
	private int serverId = -1, ProtocolVersion = 0;
	private boolean _isAuthed;
	private GameServerInfo gameServerInfo;
	private long pingRequested = 0, lastPingResponse = 0;
	private int _online = 0;

	public AttGS(SelectionKey sc)
	{
		key = sc;
		new KeyTask(this).start();

		if(GSConnection.DEBUG_LS_GS)
			log.info("LS Debug: RSAKey task started");
	}

	private void requestPing()
	{
		pingRequested = System.currentTimeMillis();
		sendPacket(new TestConnection()); // посылаем пинг
	}

	public void notifyPingResponse()
	{
		lastPingResponse = System.currentTimeMillis();
		pingRequested = 0;
	}

	public boolean isPingTimedOut()
	{
		long now = System.currentTimeMillis();
		if(pingRequested == 0)
		{
			if(now - lastPingResponse > 500)
				requestPing();
			return false;
		}

		return now - pingRequested > Config.LOGIN_WATCHDOG_TIMEOUT;
	}

	public void sendPacket(ServerBasePacket packet)
	{
		try
		{
			if(!key.isValid())
				return;

			if(GSConnection.DEBUG_LS_GS)
				log.info("LS Debug: adding packet to sendQueue: " + packet.getClass().getName());

			synchronized (sendQueue)
			{
				sendQueue.add(packet);
			}
			key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);

			if(GSConnection.DEBUG_LS_GS)
				log.info("LS Debug: Packet added");
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	public void onClose()
	{
		try
		{
			if(isAuthed())
			{
				setAuthed(false);
				log.info("LoginServer: Connection with gameserver " + getServerId() + " [" + getName() + "] lost.");
			}

			GSConnection.getInstance().removeGameserver(this);
			sendQueue.clear();
			if(gameServerInfo != null)
				gameServerInfo.setDown();
			gameServerInfo = null;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	public ByteBuffer getReadBuffer()
	{
		return readBuffer;
	}

	public ArrayDeque<ServerBasePacket> getSendQueue()
	{
		return sendQueue;
	}

	public byte[] encrypt(byte[] data) throws IOException
	{
		if(crypt == null)
			return data;
		return crypt.crypt(data);
	}

	public byte[] decrypt(byte[] data) throws IOException
	{
		if(crypt == null)
			return data;
		return crypt.decrypt(data);
	}

	public void processData()
	{
		try
		{
			ByteBuffer buf = getReadBuffer();

			int position = buf.position();
			if(position < 2) // У нас недостаточно данных для получения длинны пакета
				return;

			// Получаем длинну пакета
			int lenght = Util.getPacketLength(buf.get(0), buf.get(1));

			// Пакетик не дошел целиком, ждем дальше
			if(lenght > position)
				return;

			byte[] data = new byte[position];
			for(int i = 0; i < position; i++)
				data[i] = buf.get(i);

			buf.clear();

			while((lenght = Util.getPacketLength(data[0], data[1])) <= data.length)
			{
				data = processPacket(data, lenght);
				if(data.length < 2)
					break;
			}

			buf.put(data);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	private byte[] processPacket(byte[] data, int lenght)
	{
		try
		{
			byte[] remaining = new byte[data.length - lenght];
			byte[] packet = new byte[lenght - 2];

			System.arraycopy(data, 2, packet, 0, lenght - 2);
			System.arraycopy(data, lenght, remaining, 0, remaining.length);

			ClientBasePacket runnable = PacketHandler.handlePacket(packet, this);
			if(runnable != null)
			{
				if(GSConnection.DEBUG_LS_GS)
					log.info("LoginServer: Reading packet from GS [" + getServerId() + "]: " + runnable.getClass().getSimpleName());
				ThreadPoolManager.getInstance().executeLSGSPacket(runnable);
			}

			return remaining;
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return new byte[] {};
		}
	}

	public void initBlowfish(byte[] key)
	{
		crypt = key == null ? ConnectionCryptDummy.instance : new NewCrypt(key);
		log.info("Init connection crypt for gameserver " + getConnectionIpAddress() + ": " + crypt.getClass().getSimpleName());
	}

	public byte[] RSADecrypt(byte[] data) throws Exception
	{
		return rsa.decryptRSA(data);
	}

	public byte[] RSAEncrypt(byte[] data) throws Exception
	{
		return rsa.encryptRSA(data);
	}

	public byte[] getRSAPublicKey()
	{
		return rsa.getRSAPublicKey();
	}

	public void setRSA(RSACrypt rsa)
	{
		this.rsa = rsa;
	}

	public int getServerId()
	{
		return serverId;
	}

	public void setServerId(int serverId)
	{
		this.serverId = serverId;
	}

	public boolean isAuthed()
	{
		return _isAuthed;
	}

	public void setAuthed(boolean authed)
	{
		_isAuthed = authed;
	}

	public void addAccountInGameServer(String account)
	{
		synchronized (accountsInGameServer)
		{
			if(accountsInGameServer.contains(account))
				return;
			accountsInGameServer.add(account);
		}
	}

	public void addAccountsInGameServer(String... accounts)
	{
		synchronized (accountsInGameServer)
		{
			for(String account : accounts)
				if(!accountsInGameServer.contains(account))
					accountsInGameServer.add(account);
		}
	}

	public void removeAccountFromGameServer(String account)
	{
		synchronized (accountsInGameServer)
		{
			accountsInGameServer.remove(account);
		}
	}

	public boolean isAccountInGameServer(String account)
	{
		synchronized (accountsInGameServer)
		{
			return accountsInGameServer.contains(account);
		}
	}

	public void clearAccountInGameServer()
	{
		synchronized (accountsInGameServer)
		{
			accountsInGameServer.clear();
		}
	}

	public int getPlayerCount()
	{
		return _online;
	}

	public void setPlayerCount(int i)
	{
		_online = i;
	}

	public int getProtocolVersion()
	{
		return ProtocolVersion;
	}

	public void setProtocolVersion(int ver)
	{
		ProtocolVersion = ver;
	}

	public GameServerInfo getGameServerInfo()
	{
		return gameServerInfo;
	}

	public void setGameServerInfo(GameServerInfo gameServerInfo)
	{
		this.gameServerInfo = gameServerInfo;
	}

	public String getName()
	{
		return GameServerTable.getInstance().getServerNames().get(getServerId());
	}

	public String getConnectionIpAddress()
	{
		SocketChannel channel = (SocketChannel) key.channel();
		return channel.socket().getInetAddress().getHostAddress();
	}

	public void kickPlayer(String account)
	{
		sendPacket(new KickPlayer(account));
		removeAccountFromGameServer(account);
		LoginController.getInstance().removeAuthedLoginClient(account);
	}

	public SelectionKey getSelectionKey()
	{
		return key;
	}

	public boolean isCryptInitialized()
	{
		return crypt != null;
	}

	@Override
	public String toString()
	{
		return "AttGS - " + gameServerInfo;
	}
}